import {
    AxesHelper, Camera, Clock,
    DirectionalLight, GridHelper,
    HemisphereLight, Layers,
    Object3D,
    PerspectiveCamera,
    Scene, Vector2, Vector3,
    WebGLRenderer
} from 'three'
import {OrbitControls} from "three/examples/jsm/controls/OrbitControls"
import Stats from 'three/examples/jsm/libs/stats.module'
import {type CSS3DObject, CSS3DRenderer} from "three/examples/jsm/renderers/CSS3DRenderer";
import {MapControls} from "three/examples/jsm/controls/MapControls";
import type {Model} from "./model/Model.ts";
import {LayerDef} from "./LayerDef.ts";


export class World {
    defaultCamera: Vector3= new Vector3(0.01, 12.0, 0.01)
    layers: Layers = new Layers()
    scene!: Scene
    camera!: Camera
    renderer!: WebGLRenderer
    css3dRenderer!: CSS3DRenderer
    controls!: OrbitControls
    css3dControls!: OrbitControls
    stats!: Stats
    statsDisplayCss: string = ''
    container!: Element

    controlCategory: string

    clock: Clock
    instanceContainer: Scene[]

    static updateFrequency: number = 20

    updateCount: number = 0
    updateDelta: number = 0

    constructor(selector: string) {
        this.controlCategory = "orbitControls"
        this.controlCategory = "mapControls"
        let c = document.querySelector(selector)
        if (c == null) {
            console.log("no target", selector)
            return
        }
        this.container = c!
        this.layers.disable()
        let size = this.getSize()
        console.log("world size", size.x, size.y)
        this.clock = new Clock()
        this.instanceContainer = []
        this.init()
        this.animateLoop()
        //
        // setInterval(()=>{
        //     this.resetCamera()
        // }, 1000*60*30)

        this.initLayer()
    }

    initLayer() {
        this.camera.layers.disableAll()
        let supportedLayers = LayerDef.getAllLayers()
        console.log('支持的layers')
        let i = 0
        for (let layer of supportedLayers) {
            const name = LayerDef.getHelperName(layer)
            console.log('  ', layer, name)
            setTimeout(() => {
                console.log('enable layer', layer, name)
                this.camera.layers.enable(layer)
            }, 1000+i*100)
            i++
        }
    }

    /**
     * world scene的物理尺寸, 由外部指定
     */
    getSize(): Vector2 {
        let node = this.container.parentElement as HTMLElement
        return new Vector2(node.clientWidth, node.clientHeight)
    }

    /**
     * 在world scene里添加3D实例
     * @param obj 3D实例
     */
    public add(obj: Object3D|undefined){
        if (obj != undefined) {
            this.scene?.add(obj)
        }
    }

    /**
     * 在world scene里添加模型实例
     * @param obj 模型实例
     */
    public addModel(obj: Model|undefined){
        if (obj != undefined) {
            this.scene?.add(obj.scene)
            if (obj.updateEnable) {
                this.instanceContainer.push(obj)
            }
        }
    }

    init() {
        // 初始化场景
        this.initScene()
        // 初始化辅助轴
        this.initAxesHelper()
        // 初始化灯光
        this.initLight()
        // 初始化相机
        this.initCamera()
        // 初始化渲染器
        this.initRender()
        // 初始化轨道控制器
        this.initControls()
        // 监听场景大小改变，重新渲染尺寸
        window.addEventListener('resize',this.onWindowResize.bind(this))

        this.stats = new Stats()
        document.body.appendChild(this.stats.dom)
        this.statsDisplayCss = this.stats.dom.style.display
        this.statsStyle(0)
    }

    /**
     * 0.hide 1.show(fps) 2.showAll
     */
    statsStyle(style: number) {
        switch (style) {
            case 0:
                this.stats.dom.style.display = 'none'
                break
            case 1:
                this.stats.showPanel(0) //fps
                this.stats.dom.style.display = this.statsDisplayCss
                break
            case 2:
                this.stats.dom.style.display = this.statsDisplayCss
                break
        }
    }

    animateLoop() {
        this.animate()
    }

    initScene() {
        this.scene = new Scene()
        this.scene.background = null

        let gridHelper = new GridHelper(100, 100, 0x2c2c2c, 0x888888)
        gridHelper.layers.set(LayerDef.helper)
        // gridHelper.rotateX(Math.PI/2)
        // this.scene.add(gridHelper)
        // this.scene.rotateY(Math.PI/4)
    }

    initAxesHelper() {
        const axesHelper = new AxesHelper(5)
        axesHelper.layers.set(LayerDef.helper)
        this.scene.add(axesHelper)
    }

    initLight() {
        const hesLight = new HemisphereLight(0xffffff,0x444444)
        hesLight.intensity = 0.6
        hesLight.layers.set(LayerDef.default)
        this.scene.add(hesLight)

        const dirLight = new DirectionalLight()
        dirLight.position.set(5,5,5)
        dirLight.layers.set(LayerDef.default)
        this.scene.add(dirLight)
    }

    initCamera() {
        let size = this.getSize()
        this.camera = new PerspectiveCamera(75,size.x / size.y,0.1,100)
        this.setCamera()
    }

    /**
     * 调整视角, 不指定坐标时使用默认视角
     * @param pos 指定视角坐标
     */
    setCamera(pos: Vector3|undefined) {
        if (!pos) {
            pos = this.defaultCamera
        }
        this.camera.position.set(pos.x, pos.y,pos.z)
        this.camera.quaternion.set(0, 0, 0, 1)
        this.camera.lookAt(new Vector3(0, 0, 0))
    }

    resetCamera() {
        this.controls.enabled = false
        this.css3dControls.enabled = false
        this.setCamera()
        this.camera.updateMatrixWorld()
        this.controls.target.set(0, 0, 0)
        this.css3dControls.target.set(0, 0, 0)
        this.controls.update()
        this.css3dControls.update()
        this.controls.enabled = true
        this.css3dControls.enabled = true
    }

    initRender() {
        let size = this.getSize()
        this.renderer = new WebGLRenderer({antialias:true,alpha:true})//设置抗锯齿
        this.renderer.setClearAlpha(0);
        this.css3dRenderer = new CSS3DRenderer()

        this.renderer.setPixelRatio(window.devicePixelRatio)

        this.renderer.setSize(size.x,size.y)
        this.css3dRenderer.setSize(size.x,size.y)
        this.css3dRenderer.domElement.style.position = 'absolute';
        this.css3dRenderer.domElement.style.top = '0px';
        this.css3dRenderer.domElement.style.pointerEvents = 'none';
        // 添加到容器
        this.container.appendChild(this.renderer.domElement)
        this.container.appendChild(this.css3dRenderer.domElement)
    }

    render() {
        let delta = this.clock.getDelta()
        this.stats.update()
        this.updateModels(delta)
        this.renderer.render(this.scene,this.camera)
        this.css3dRenderer.render(this.scene,this.camera)
    }

    updateModels(delta: number) {
        this.updateDelta += delta
        this.updateCount++
        if (this.updateCount < World.updateFrequency) {
            return
        }
        for (const item of this.instanceContainer) {
            item.update(this.updateDelta)
        }
        this.updateDelta = 0
        this.updateCount = 0
    }

    animate() {
        this.renderer.setAnimationLoop(this.render.bind(this))
    }

    initControls() {
        if (this.controlCategory == "mapControls") {
            console.log("init trackball controls")
            this.controls = new MapControls(this.camera,this.renderer.domElement)
            this.css3dControls = new MapControls(this.camera, this.css3dRenderer.domElement)
        } else {
            this.controls = new OrbitControls(this.camera,this.renderer.domElement)
            this.css3dControls = new OrbitControls(this.camera, this.css3dRenderer.domElement)
        }

    }

    onWindowResize() {

        let size = this.getSize()
        this.renderer.setSize(size.x,size.y)

        let c = this.camera as any
        c.aspect = size.x / size.y
        c.updateProjectionMatrix()
    }
    //
    createCss3dObj(div: HTMLElement):CSS3DObject {
        return new CSS3DObject(div)
    }
}
